package adm

import (
	"reflect"
	"git.oschina.net/janpoem/go-agi"
)

type ModelMapping struct {
	// 主键的字段，实际上指向的是一个ModelColumn
	Pk              *ModelColumn
	// 主键的模式
	PkMode          PkMode
	// 映射的字段
	Columns         []*ModelColumn
	// 字段的总数
	Size            int
}

type ModelColumn struct {
	// 结构的属性名
	Name            string
	// 数据库存储的字段名
	Column          string
	// 该字段在Model的属性集合中的Index，用于从一个Model实例中快速获取该字段
	Index           int
}

// 预定32个mapping缓冲
var models = make(map[reflect.Type]*ModelMapping, 32)

func GetModelMapping(obj Model) *ModelMapping {
	tp := reflect.TypeOf(obj)
	tpEl := tp.Elem()

	m, exists := models[tpEl]
	if exists {
		return m
	}

	pkHit := false
	pkMode := PkNone
	var pk *ModelColumn
	columns := make([]*ModelColumn, 0)
	for i := 0; i < tpEl.NumField(); i++ {
		field := tpEl.Field(i)
		tag := field.Tag
		tagCol := tag.Get(TagColumn)

		if tagCol == TagValNothing {
			continue
		}

		col := &ModelColumn{Name: field.Name, Column: tagCol, Index: i }

		pkSetting := tag.Get(TagPk)
		if pkHit == false && pkSetting != TagValNothing {
			pkHit = true
			pkMode = detectPkMode(pkSetting)
			pk = col
		}

		columns = append(columns, col)
	}

	m = &ModelMapping{Pk: pk, PkMode: pkMode, Columns: columns, Size: len(columns) }
	models[tpEl] = m

	return m
}

func AssignSlice(obj Model, values []interface {}) {
	mapping := GetModelMapping(obj)
	valRef := reflect.ValueOf(obj).Elem()
	for i := 0; i < len(values); i++ {
		col := mapping.Columns[i]
		assignFieldValue(valRef.Field(col.Index), values[i])
	}
}

func detectPkMode(mode string) PkMode {
	if mode == TagValPkAi {
		return PkAi
	}
	return PkNormal
}

// 基于数据库字段，来写入对象
// 从map -> column的匹配，需要两次，所以，还是从本地的字段长度来匹配
func assignMap(obj Model, m map[string]interface{}, useColumn bool) {
	mapping := GetModelMapping(obj)
	valRef := reflect.ValueOf(obj).Elem()
	for i := 0; i < len(mapping.Columns); i++ {
		col := mapping.Columns[i]
		var val interface{}
		var exists bool
		if useColumn {
			val, exists = m[col.Column]
		} else {
			val, exists = m[col.Name]
		}
		if exists {
			assignFieldValue(valRef.Field(col.Index), val)
		}
	}
}

func AssignMap(obj Model, m map[string]interface{}) {
	assignMap(obj, m, true)
}

func AssignMapByField(obj Model, m map[string]interface{}) {
	assignMap(obj, m, false)
}

func AssignStruct(obj Model, target interface {}) {
	if agi.KindOf(target) != reflect.Struct {
		return
	}

	mapping := GetModelMapping(obj)
	valRef := reflect.ValueOf(obj).Elem()
	targetRef := agi.ValueOf(target)

	for i := 0; i < len(mapping.Columns); i++ {
		col := mapping.Columns[i]
		targetField := targetRef.FieldByName(col.Name)
		if targetField.IsValid() {
			assignFieldValue(valRef.Field(col.Index), targetField.Interface())
		}
	}
}

func isSimpleNumber(field reflect.Value) bool {
	switch field.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64:
		return true
	}
	return false
}

func isExcept(field reflect.Value) bool {
	kind := field.Kind()
	return kind == reflect.Slice || kind == reflect.Array || kind == reflect.Map || kind == reflect.Struct
}

func assignFieldValue(field reflect.Value, value interface{}) {
	// 跳过特定类型的字段
	if !field.IsValid() {
		return
	}
	valueOf := agi.ValueOf(value)
	fieldType := field.Type()
	fieldKind := field.Kind()
	// 字段类型为数字的转换处理
	if isSimpleNumber(field) {
		// 跳过特定类型的值
		if isExcept(valueOf) {
			return
		}
		// 简单数字类型，直接使用反射的转换处理
		if isSimpleNumber(valueOf) {
			field.Set(valueOf.Convert(fieldType))
		} else {
			field.Set(agi.ValueOf(agi.AnyToFloat(value)).Convert(fieldType))
		}
		return
	}
	// 字段类型为字符的转换处理
	if fieldKind == reflect.String {
		field.SetString(agi.AnyToStr(value))
		return
	}
	// 布尔类型转换
	if fieldKind == reflect.Bool {
		field.SetBool(agi.AnyToBool(value))
		return
	}
	// 时间类型
	if fieldType.String() == "time.Time" {
		field.Set(reflect.ValueOf(agi.AnyToTime(value)))
		return
	}
	// @todo 这里应该还有一些类型要做转换处理的
}

// 判断是否有有效主键值
func PkValid(obj Model) bool {
	mapping := GetModelMapping(obj)
	// 无主键的话，就返回false
	if mapping.PkMode == PkNone {
		return false
	}
	valRef := reflect.ValueOf(obj).Elem()
	field := valRef.Field(mapping.Pk.Index)
	kind := valRef.Kind() // 取得主键的值类型，现在只判断两种，字符串和整型
	val := field.Interface()
	iVal := agi.AnyToInt64(val) // 整型需要两次被使用

	// 自增的主键，默认认为主键必须为整型的数值，并且必须大于0，则认为是有效的主键
	if mapping.PkMode == PkAi {
		return iVal > 0
	}
	// 艾玛，整型也很多好不好
	// 暂时将整型值的使用缩小在这几个范围内，uint16最大也就是65535，可能作为主键吗？
	// 整型的话，大于0即可
	switch kind {
		case reflect.Int, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint32, reflect.Uint64 :
		return iVal > 0
		// 剩下的，就是正常模式下的主键，那我们就将他转为字符串，且字符串不为空为判断有效性
		default :
		return len(agi.AnyToStr(val)) > 0
	}
	return false
}